﻿using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.GridFS;
using System;
using System.IO;
using System.Linq;

namespace Sustainalytics.DataLayer
{
    public class MongoGridFSHelper
    {
        private readonly GridFSBucket _gridFSBucket;

        public MongoGridFSHelper(IMongoDatabase database)
        {
            _gridFSBucket = new GridFSBucket(database, new GridFSBucketOptions()
            {
                ChunkSizeBytes = 261120
            });
        }

        public byte[] DownloadToByteArray(Guid id)
        {
            var fileInfo = _gridFSBucket.FindOneById(id);
            if (fileInfo == null)
                return new byte[0];

            return _gridFSBucket.DownloadAsBytes(fileInfo.Id);
        }

        public Tuple<Stream, string, string> OpenDownloadStream(Guid id)
        {
            var fileInfo = _gridFSBucket.FindOneById(id);
            if (fileInfo == null)
                return null;

            var downloadStream = _gridFSBucket.OpenDownloadStream(fileInfo.Id);
            var contentType = fileInfo.ContentType;
            if (fileInfo.Metadata != null)
                contentType = fileInfo.Metadata.GetElement("contentType").Value.AsString;

            return new Tuple<Stream, string, string>(downloadStream, fileInfo.Filename, contentType);
        }

        // stream write and read
        public Tuple<Stream, Guid> UpdateFile(string fileName, Guid id, Guid newid, string contentType)
        {
            //todo petre reimplement by returning stream and reset lenght
            DeleteFile(id);
            return OpenUploadStream(fileName, newid, contentType);
        }

        public GridFSFileInfo FindOneById(Guid id)
        {
            return _gridFSBucket.FindOneById(id);
        }

        public Tuple<Stream, Guid> OpenUploadStream(string fileName, Guid id, string contentType)
        {
            var options = GetGridFsUploadOptions(id, contentType);
            var uploadStream = _gridFSBucket.OpenUploadStream(fileName, options);
            return new Tuple<Stream, Guid>(uploadStream, id);
        }

        public void DeleteFile(Guid id)
        {
            var fileInfo = _gridFSBucket.FindOneById(id);
            if (fileInfo == null)
                return;

            _gridFSBucket.Delete(fileInfo.Id);
        }

        private void DeleteFile(ObjectId id)
        {
            _gridFSBucket.Delete(id);
        }

        public void DeleteAllFiles()
        {
            var filter = Builders<GridFSFileInfo>.Filter.Empty;

            using (var cursor = _gridFSBucket.Find(filter))
            {
                cursor.ToList().ForEach(x =>
                {
                    DeleteFile(x.Id);
                });
            }
        }

        private static GridFSUploadOptions GetGridFsUploadOptions(Guid id, string contentType)
        {
            return new GridFSUploadOptions
            {
                Metadata = new BsonDocument
                {
                    { "contentType", contentType },
                    { "_id", id }
                }
            };
        }
    }

    public static class GridFsBucketExtensions
    {
        public static GridFSFileInfo FindOneById(this GridFSBucket gridFSBucket, BsonValue id)
        {
            GridFSFileInfo fileInfo = null;

            var filter = Builders<GridFSFileInfo>.Filter.Eq("_id", id.AsGuid);
            var options = new GridFSFindOptions { Limit = 1 };

            using (var cursor = gridFSBucket.Find(filter, options))
            {
                fileInfo = cursor.ToList().FirstOrDefault();
            }

            if (fileInfo == null)
            {
                filter = Builders<GridFSFileInfo>.Filter.Eq(x => x.Metadata["_id"], id.AsGuid);
                using (var cursor = gridFSBucket.Find(filter, options))
                {
                    fileInfo = cursor.ToList().FirstOrDefault();
                }
            }

            return fileInfo;
        }
    }
}
